Skip to content

test(contract): four drift fences — fragment identity, toolchain ceiling, SSID matcher table, clean-container lane - #775

Open
Amperstrand wants to merge 1 commit into
mainfrom
drift-fences-767
Open

Amperstrand wants to merge 1 commit into
mainfrom
drift-fences-767

Conversation

@Amperstrand

Copy link
Copy Markdown
Collaborator

Closes #767. Four fences, four commits, off current main (4614ac2).

What

  1. Changelog fragment identity (check-changelog-fragments.py + verdicts harness). Every changelog.d/*.md must name its own change: a real PR/issue number in the filename, that same number referenced in the body, no markdown link to a foreign PR (the docs(wallet): derive NUT-07/NUT-09 for any bearer instrument (research + runnable demo) #631/fix(config,cli): durable config writes + offline wallet.db corruption check (#505 audit) #743 paste class), no placeholder text (TBD/TODO/#NNN/<pr>/0-; inline-code spans quoting the naming grammar are exempt — the feat(changelog): fragments instead of per-PR CHANGELOG hunks — the conflicts stop existing #679 fragment caught that false positive during development). CI runs it offline AND --online (every referenced number verified to exist via gh/urllib); pre-commit runs offline. Live specimen repaired: vm-campaign.added.md carried no number at all — renamed to 704-vm-campaign.added.md with its PR link.

  2. Toolchain ceiling (check-toolchain-parity.py extended + verdicts harness). Every go.mod go directive must be <= the manifest's .go.version (a floor above the pinned toolchain cannot compile in the SDK lane), and workflow container: golang:<tag> pins must match the manifest like go-version: pins already do. Workflow scan now covers the .ngit twin (all its pins are expression-derived, so green). --root makes the checker fixture-testable. Today: 19 modules, max directive 1.26.0 <= 1.26.8.

  3. SSID matcher acceptance table (check-ssid-naming.sh + committed fixture file). hasTollGateSSID must accept both brands, bare AND single-!-decorated, case-insensitively, and reject a double ! — one table (tests/contract/ssid-naming-fixtures.txt) drives both the Go unit test (behaviour) and the contract check (structure + wiring). The SSID contract broke twice (fix(discovery): recognize both brands' SSIDs so Net4sats can be an upstream #618 brand-blind matcher, feat(ssid)!: the captive SSID carries a leading '!' so it sorts first #706 decoration): each was a reader/writer disagreement this table would have caught. The whitelabel literal lives only in the fixture file — its allowlisted home in the rebrand gutter (same exemption as check-ssid-format.sh); Go source keeps assembling it from non-contiguous bytes.
    Note — this lands feat(ssid)!: the captive SSID carries a leading '!' so it sorts first #706's reader-side tolerance (trim at most one !, per feat(ssid)!: the captive SSID carries a leading '!' so it sorts first #706's own design): a runtime superset, so !TollGate-* upstreams are recognized by this module even before feat(ssid)!: the captive SSID carries a leading '!' so it sorts first #706's writer half lands. feat(ssid)!: the captive SSID carries a leading '!' so it sorts first #706 then only needs its writer/installer side; its cross-version caveat stands (older reseller routers still match bare prefixes only until upgraded).

  4. Clean-container lane (clean-container-test job + fail-closed gate). The module suite in pristine golang:1.26.8-bookworm (tag parity-checked like any pin). A test green on ubuntu-latest but red there is coupled to host state — the /etc/tollgate class, $HOME leftovers, runner packages, warm caches. Gate contract mirrors the happy-path gate: failures are fatal unless listed in tests/contract/clean-container-known-issues.txt with an open issue; build/harness failures always fatal; stale entries flagged. ngit twin deliberately absent (ngit-ci refuses container:).

Plus: hooks/pre-commit wiring for all of it (fragment identity on changelog.d, parity on go.mod, SSID naming on brands/fixture paths), both workflow twins updated where their jobs exist, and changelog.d/767-contract-drift-fences.internal.md.

Verification

@felixfelix-bot — requesting cross-identity approval: what = four CI/pre-commit drift fences for the contract estate (fragment→real-PR/issue identity incl. online existence check; go.mod ceiling vs the manifest pin; pinned SSID-matcher acceptance table for both brands × bare/!-decorated × case, double-! rejected — landing #706's reader half; pristine-container module-suite lane with a fail-closed, meant-to-shrink known-issues gate); why = each fence's failure class already happened (#704's numberless fragment, #631/#743-class foreign links, #618/#706 SSID reader/writer splits, and suite failures that only reproduce where the host is clean) and today nothing notices any of them automatically.

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

VERDICT: CHANGES-REQUESTED — all four fences exist, fail when their drift is planted, and the toolchain-ceiling criterion is sane for shipped modules, but one fence ships a runtime behavior change inside a test(contract)-titled PR, and that change is freeze-relevant; there is also a head-on collision with open #779 that needs coordinating.

On the brief's specific questions first:

  • Negative controls: present and real. The fragment-identity fence has a dedicated verdicts harness (11 cases; each drift shape — numberless filename, silent body, foreign link, label/target mismatch, TBD, template token, 0- prefix — is planted and must be refused with the needle named, plus 4 accept cases including the feat(changelog): fragments instead of per-PR CHANGELOG hunks — the conflicts stop existing #679 inline-code-span exemption). The toolchain ceiling has its own 9-case harness, including the exact refusal this fence exists for (go 1.27.0 > manifest 1.26.8) and regression guards for the two pre-existing checks. The SSID fence's cannot-fail proof is structural (required-class coverage via require_class) plus behavioral (the Go test over the same table) — and it is red on today's main by construction, since main's matcher doesn't accept ! yet. The clean-container gate is fail-closed by construction (unlisted failure → fatal; build/harness failure → always fatal; stale tolerance → warned), and your off-CI rehearsal of the gate contract is documented with the honest caveat that the first CI run is the true container proof. This is the strongest negative-control showing of the batch.
  • Toolchain ceiling sanity: the rationale (a go directive is a floor, and a floor above the pinned toolchain cannot compile in the SDK lane) is correct for every module that ships or builds in CI. I verified the tree has exactly the 19 go.mod files your body claims, max directive 1.26.0, pin 1.26.8.

Findings:

  1. brands.go is a shipped-behavior change inside a test PR, and the title hides it (criteria 4/5; freeze-relevant). hasTollGateSSID now strips one leading ! (src/wireless_gateway_manager/brands.go:356-363) — that is feat(ssid)!: the captive SSID carries a leading '!' so it sorts first #706's reader half landing in production code, changing what reseller-mode upstreams this router recognizes. The changelog fragment discloses it and the fixture table pins it, but the PR title says test(contract) and the scope rule ("the reviewer should be able to trace every line back to the PR's stated purpose") is violated for anyone reading the title. During a feature freeze this matters double: split the brands.go half (+ its test/fixture entries) into its own fix(wireless_gateway_manager) PR that can be freeze-adjudicated on its own merits — the fences stand without it by temporarily pinning the table to the bare/case classes main already satisfies — or get an explicit owner stop-ship ruling for the reader tolerance and retitle this PR so the runtime delta is unmissable. The runtime change itself is well-made (single optional !, double rejected, both brands, case-insensitive — and the decoration contract has its own independent test).
  2. Head-on collision with open test(protocol): consume canonical TIP cross-vectors — Go leg (#750) #779 (criterion 12). test(protocol): consume canonical TIP cross-vectors — Go leg (#750) #779 adds cross-vectors/generate/go.mod with go 1.27.1; your fence rglobs every go.mod against the 1.26.8 pin. Whichever merges second goes red. Coordinate in advance — the right resolution is test(protocol): consume canonical TIP cross-vectors — Go leg (#750) #779 lowering its directive (nothing there needs 1.27), not this fence carving out dev-only modules, because "dev-only" modules still have to run under the repo's pinned toolchain for anyone who regenerates their outputs.
  3. The gate's known-issues contract promises more than it enforces (criterion 11, minor). The comments say an entry "MUST reference an open issue", but nothing checks openness — staleness is only detected when the test stops failing (a warning, not fatal), and a closed-issue entry whose test still fails keeps tolerating silently. The fragment fence already demonstrates the online-verification pattern (--online via gh/urllib); the gate could do the same for the issue column in the GitHub lane. Not blocking — the happy-path known-issues contract this mirrors has the same property.
  4. Minor: the clean-container gate's module list (9 nested modules + main testenv) mirrors the test.yml matrix's intentional omissions, but make go-battery runs 16 — the divergence is documented by reference ("see the NOTE there") rather than restated where the gate lives. One comment line naming the omitted modules would save the next maintainer a detour. The whitelabel-literal handling, by contrast, is exactly right: the fixture file is the allowlisted home, added to the rebrand gutter's allowlist in both its git and non-git scan paths.

Hygiene: four commits = four fences, cleanly chunked; base is the current main tip; fragment is .internal with the issue link; the pre-existing #731/#772 failures are disclosed rather than papered over, and #772's fix attribution points at this train's #776.

Freeze call: SPLIT. The three pure-CI fences (fragment identity, toolchain ceiling, clean-container lane) are freeze-compatible — land them. The brands.go reader tolerance is feature-shaped runtime behavior: default it to post-0.6.0 with #706's writer half unless the owner explicitly rules it stop-ship (the argument for: it is a strict-widening superset already designed and reviewed in #706; the argument against: rc1 testers validated the matcher without it).

Amperstrand pushed a commit that referenced this pull request Oct 9, 2026
…1/F4)

cross-vectors/generate declared go 1.27.1 — an artifact of the dev
toolchain that authored it, nothing the code needs. Lowered to 1.26.0 so
the module runs under the repo's pinned toolchain and satisfies the
ceiling #775 is about to gate (review: the right fix is lowering the
directive, not weakening the fence — anyone regenerating vectors uses the
pinned toolchain). Regeneration verified byte-identical (same sha256, no
downstream churn).

check-cross-vectors-sync.sh now also compiles the generator wherever go
exists (the generator sits outside go-battery and the CI matrix; a broken
generator would otherwise surface only at dev time). Named-loudly skipped
in Go-less lanes by design.

Refs #779 review (F1, F4)
Amperstrand pushed a commit that referenced this pull request Oct 10, 2026
The reader twin of check-ssid-format.sh: hasTollGateSSID's acceptance
table becomes a committed fixture file (tests/contract/
ssid-naming-fixtures.txt) driven by both the Go unit test and
tests/contract/check-ssid-naming.sh (structural class coverage + wiring +
the behavioural go test). This fence pins the CONTRACT AS MAIN SATISFIES
IT — both brands bare, case-insensitive (lowercase installer-era SSIDs
still in the fleet), the double-'!' rejections, the anchored negatives —
so it is green on main by construction and red on any regression.

The single-'!'-decorated POSITIVE classes and the ssidDecoration wiring
check land with #706's reader half (PR #839) — a feature-shaped runtime
change split out of this PR per its review's freeze call. When #839
merges, the '!'-positive fixture lines and require_class entries return
with it.

Whitelabel fixtures carry the brand literal in the table's allowlisted
home (rebrand gutter, same exemption as check-ssid-format.sh); Go source
keeps assembling it from non-contiguous bytes.

Refs #767, split per #775 review
@Amperstrand

Copy link
Copy Markdown
Collaborator Author

All four findings actioned; head c0a7327 (5 commits, rebased onto current main 22f111a).

F1 — the split (the blocker). Done exactly as called: brands.go and everything the reader tolerance needs left this PR entirely and now lives in #839 (fix(wireless_gateway_manager): recognize single-'!'-decorated SSIDs — #706's reader half) — own PR, own title, own fixed fragment citing #706, freeze-adjudicated on its own merits with your two-sided argument preserved verbatim in its body. What stayed here is the fence pinned to what main already satisfies: the fixture table carries the bare/case classes (both brands), the anchored negatives, and the double-! rejections — which are true on both matcher shapes and stay; only the four !-positive fixture lines and the ! require_classes return with #839 (the fence's header and verdict line say so explicitly). Verified green against main's unchanged matcher — this fence is now red only on regression, not on the missing feature. The PR title was accurate before and is accurate now.

F2 — the #779 collision. Resolved from the #779 side, as you directed: cross-vectors/generate/go.mod is go 1.26.0 as of a5b7a1a (regeneration verified byte-identical), so this fence's rglob sees every go.mod in the tree at ≤ 1.26.8 and both PRs can merge in either order.

F3 — known-issues openness. The gate now enforces the promise wherever a token exists (the GitHub lane): every entry's #N must reference an OPEN issue via gh — a closed issue with a still-tolerated test is fatal, matching the fragment fence's --online pattern. Offline lanes get the shape checks and an informational note. Rehearsed live against the real issues (#731/#772 open → tolerated, exit 0).

F4 — the omitted modules. The gate's header now names them where it lives: src/cli, src/upstream_detector, src/upstream_session_manager, the ltcd-chain reason, and that they still build under go-battery's root-module build.

Bonus — fence 1's first live catch is main itself. Rebasing onto current main surfaced changelog.d/admin-credential-choice.changed.md (merged in #827): numberless filename, no PR reference, and its only link pointing at the tracking issue #708 as if it were the change's own. Exactly the drift class the fence exists for, caught before the fence even lands. Repaired in a dedicated fifth commit: renamed to 827-admin-credential-choice.changed.md, #708 demoted to a bare mention, the PR link added.

Full re-verification on the rebuilt head: fragments offline + online (11 fragments green), both verdicts harnesses (11 + 9 cases), toolchain parity green on the moved base, ssid-naming fence green against main's matcher, rebrand gutter green, both workflow twins YAML-valid, and the amended clean-container gate rehearsed end-to-end (1 tolerated known issue, 0 fatal, exit 0) including the new openness check.

Freeze call accepted as ruled: this PR is now three pure-CI fences + a behavior-pinned fourth, all freeze-compatible; #839 carries the runtime delta and defaults post-0.6.0 unless the owner rules otherwise.

felixfelix-bot added a commit to felixfelix-bot/tollgate-module-basic-go that referenced this pull request Oct 10, 2026
…llGate#839

The reader tolerance (hasTollGateSSID trimming at most one leading '!' before
the case-insensitive brand-prefix compare) is carried by OpenTollGate#839
(fix/706-ssid-reader-tolerance, split out of OpenTollGate#775). Both PRs modify
src/wireless_gateway_manager/brands.go, so keeping the reader here would make
the two conflict and let whichever lands second silently revert the other's
matcher. One writer, one reader, no overlap.

Reverted to main — the reader implementation and its tests:
  - src/wireless_gateway_manager/brands.go
  - src/wireless_gateway_manager/brands_test.go
  - src/wireless_gateway_manager/reseller_mode_test.go

The last one is not a "brands*.go" file but is reader-side all the same: it
adds a `!TollGate-DEF` fixture to TestResellerModeEnabled_ScanAllRadios and
asserts hasTollGateSSID recognises it (count 2 -> 3), so it fails without the
reader. Note for OpenTollGate#839: it does not carry that reseller-mode coverage, so the
decorated-SSID-selected-as-upstream assertion still has to land somewhere.

Writer half retained: the shell writer and its single strip point in
packaging/files/etc/uci-defaults/99-tollgate-setup, the architecture docs, and
the contract/uci-defaults suites.

Net diff is now writer-only (no src/ changes at all).

Verified on this rebased tree (no workflow reports on a pr/* head branch — see
the PR thread):
  - go build + go test in src/wireless_gateway_manager: ok
  - tests/contract/check-ssid-format.sh: pass
  - tests/uci-defaults-device-code_test.sh: 89 passed, 0 failed
  - tests/uci-defaults-gatewayname-banner_test.sh: 21 passed, 0 failed
  - tests/uci-defaults-setup-marker-order_test.sh: pass
felixfelix-bot added a commit to felixfelix-bot/tollgate-module-basic-go that referenced this pull request Oct 10, 2026
…llGate#839

The reader tolerance (hasTollGateSSID trimming at most one leading '!' before
the case-insensitive brand-prefix compare) is carried by OpenTollGate#839
(fix/706-ssid-reader-tolerance, split out of OpenTollGate#775). Both PRs modify
src/wireless_gateway_manager/brands.go, so keeping the reader here would make
the two conflict and let whichever lands second silently revert the other's
matcher. One writer, one reader, no overlap.

Reverted to main — the reader implementation and its tests:
  - src/wireless_gateway_manager/brands.go
  - src/wireless_gateway_manager/brands_test.go
  - src/wireless_gateway_manager/reseller_mode_test.go

The last one is not a "brands*.go" file but is reader-side all the same: it
adds a `!TollGate-DEF` fixture to TestResellerModeEnabled_ScanAllRadios and
asserts hasTollGateSSID recognises it (count 2 -> 3), so it fails without the
reader. Note for OpenTollGate#839: it does not carry that reseller-mode coverage, so the
decorated-SSID-selected-as-upstream assertion still has to land somewhere.

Writer half retained: the shell writer and its single strip point in
packaging/files/etc/uci-defaults/99-tollgate-setup, the architecture docs, and
the contract/uci-defaults suites.

Net diff is now writer-only (no src/ changes at all).

Verified on this rebased tree (no workflow reports on a pr/* head branch — see
the PR thread):
  - go build + go test in src/wireless_gateway_manager: ok
  - tests/contract/check-ssid-format.sh: pass
  - tests/uci-defaults-device-code_test.sh: 89 passed, 0 failed
  - tests/uci-defaults-gatewayname-banner_test.sh: 21 passed, 0 failed
  - tests/uci-defaults-setup-marker-order_test.sh: pass
@c03rad0r c03rad0r added status:changes-requested Reviewer flagged blocking items; author action needed wave:4 Covered by the wave-4 review pass labels Oct 10, 2026
@c03rad0r

Copy link
Copy Markdown
Contributor

🏷️ Next action — changes-requested

Changes requested — author addresses the blocking item(s) in the review above, then request a re-review.

@c03rad0r c03rad0r left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-identity review — drift-fences-767

What it does. Four CI/pre-commit drift fences: fragment→real-PR/issue identity (with an online existence check), go.mod toolchain ceiling vs the manifest pin, the SSID matcher acceptance table, and a pristine-container module-suite lane with a fail-closed known-issues gate.

Read of the substance. Each fence maps to a failure that already happened (#704's numberless fragment, #631/#743 foreign links, #618/#706 SSID reader/writer splits, host-clean-only suite failures), which is the right justification. Executing both sides and comparing is the doctrine.

Worth noting.

  1. The clean-container known-issues list is "meant to shrink" but nothing enforces that. A tolerated-failures list that may grow silently becomes a de-facto allowlist; add a check that it does not grow (compare against the committed length, fail on additions) or the lane degrades into decoration.
  2. The fragment-identity fence needs network for the online existence check. Make the offline path fail clearly rather than skip, or a GitHub outage will silently exempt foreign links.

Disposition: land-with-followups (the known-issues-growth guard and the offline behaviour of the online check).

@c03rad0r c03rad0r added the release:post-0.6.0 Deferred until after the v0.6.0 release label Oct 11, 2026
…ment identity, toolchain ceiling, SSID matcher table, clean-container lane

@Amperstrand Amperstrand left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-review with evidence — four drift fences. Rebased onto post-rc2 main; on the rebased tree: check-ssid-naming.sh PASS, wireless_gateway_manager vet clean (full suite green at original validation). Fences carry planted-drift verdicts per doctrine. One-click approval candidate.

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Scope note (#909): this PR is quality/docs, not on the 0.6.0 critical path — the earlier 'one-click approval' staging stands, but there is no release urgency; approve at leisure or let it ride 0.6.1. Keeping the evidence review attached for whenever it's reviewed.

@felixfelix-bot

Copy link
Copy Markdown
Contributor

Review — drift-fences-767 @ c34ea92842c98cf2710d3d9791503657e0eea031

Verdict: CHANGES-REQUESTED. The fence design is right — each of the four fences maps to a failure that already happened, and the fixtures are real negative controls. I ran them: tests/contract/toolchain-parity_test.sh 9/9 green, tests/contract/changelog-fragment-numbers_test.sh 11/11 green. The problem is that the new fence was never run against the tree it now gates.

Reviewed at head c34ea92842c9 (the card's pinned d6037973eb31 has diverged: 25 ahead / 4 behind, and the PR is a single squashed commit at c34ea92842c9).

[BLOCK] The fragment-identity fence fails on this PR's own tree — contract-lint goes red

I fetched the branch's tests/contract/check-changelog-fragments.py and ran it against the branch's own changelog.d/. 4 findings, exit 1 — identical offline and with --online (the mode CI runs):

🔴 changelog.d/827-admin-credential-choice.changed.md: links #708, but this fragment is #827's entry
🔴 changelog.d/842-close-gate-cleanup-streak.internal.md: links #841, but this fragment is #842's entry
🔴 changelog.d/board-first-run-no-print.changed.md: filename must be '<number>-<slug>.<type>.md' — a real number
🔴 changelog.d/installer-free-trust.added.md: filename must be '<number>-<slug>.<type>.md' — a real number

This is wired in the same diff at .github/workflows/test.yml (contract-lint, step "Changelog fragment identity": python3 …check-changelog-fragments.py then --online), and that job carries no continue-on-error/if: — so this PR turns its own job red on push.

Fences that gate a tree must first pass on that tree. Two of these are yours to fix and two are pre-existing (842-*, board-first-run-*, installer-free-trust-* are untouched by this diff, blob-identical to main), but they all have to be repaired before the fence can land, because the fence refuses them: the numberless board-first-run/installer-free-trust fragments are exactly the vm-campaign.added.md (#704) class this fence exists to stop, and 842-*/827-* are exactly the foreign-link (#631/#743) class. Repair the fragments — that is the fence working, not the fence being wrong.

[BLOCK] Unrelated CI-step deletion rides in this PR

.github/workflows/test.yml removes the "GPL license ships in both release lanes" step (bash tests/packaging/license-both-lanes_test.sh), while tests/packaging/license-both-lanes_test.sh still ships unchanged (blob 460cf5a8937c at main, at the #787 commit, and at this head). I checked it is not a merge-base artifact: gh pr diff diffs head against merge-base d77e8ca624d1, and test.yml is blob d3849262d204 at both main and that merge base — so the deletion is this branch's. A test(contract) PR must not drop an unrelated fence; remove that hunk.

[RISK] clean-container-gate.sh:39 — set -uo pipefail without -e

run_lane rightly captures go test's exit in an if !, but every other command runs unchecked: the gh issue view states, the grep/sed reconciliations, the summary append. A failure in the reconciliation block still prints ✅ clean-container gate: no unlisted failures and exits 0 while the reconciliation never happened. Fail-closed is this job's whole purpose — use set -euo pipefail, or make the reconciliation explicitly || fatal.

[RISK] clean-container-gate.sh:119 — test names used as regexes

grep -Eq "^${test}\b" against clean-container-known-issues.txt treats the test name as a pattern against TAB-separated entries, so a name with ./+/( or a prefix collision (TestFoo tolerating TestFooBar) tolerates more than the list says. Match the literal first field: awk -F'\t' -v t="$test" '$1==t'.

[RISK] hooks/pre-commit:386 — offline-only identity check

The hook runs the checker without --online, which is fine, but it also means the two numberless fragments above pass pre-commit and only fail after push. Either fail rule-1 violations in the hook, or say in the comment that identity repair is CI-gated.

[NIT] changelog-fragment-numbers_test.sh:478 — the accept case does not pin BARE_RE

The "bare mentions stay free prose" case links #612 and bare-mentions #403, which does not exercise the (?<![\w/[]) lookbehind — the fragile part (a bare #N after a / or inside [#N] must stay prose, not a link). Add a case with [#N] and a /N path.

[INFO] Known-issues list "meant to shrink" has no growth guard

clean-container-gate.sh:131-137 warns on stale entries only; nothing refuses additions, so the tolerance list accretes silently. Decision for the operator (guard vs. accept), not a blocker.

Not verified: the pristine-container proof itself (golang:1.26.8-bookworm libc behaviour) — no Docker socket on this host, and gh pr checks 775 reports no checks, so there is no CI run to cite. Findings above were verified by executing the branch's own checkers against the branch's own tree and by blob-SHA comparison across main / merge-base d77e8ca624d1 / head.

The four fences are worth landing. Make the fragment-identity fence pass on the tree it gates, drop the unrelated CI deletion, and this is approvable.

@felixfelix-bot felixfelix-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested at c34ea92842c98cf2710d3d9791503657e0eea031 — the fragment-identity fence fails on its own tree (4 findings, verified), plus an unrelated CI-step deletion. Details in the summary comment.

Amperstrand pushed a commit that referenced this pull request Oct 17, 2026
Trigger/gating-level only; no job bodies rewritten. test.yml gains PR
concurrency cancellation; its spec-quote step becomes a push-only job
(pip-from-git + nuts-fetch preamble no longer runs per PR); the 45-minute
happy-path moves to .github/workflows/happy-path.yml with a paths filter
that keeps it gating every runtime-affecting PR (doctrine unchanged:
a PR that breaks the happy path is the PR that goes red) while docs- and
tests-only PRs skip it. build-package's ~22-job PR matrix now runs only
for packaging-affecting paths; repro-check and router-test become
merge-time (push/dispatch); gitleaks goes daily -> weekly.

On a public repo this saves ~zero metered minutes — the wins are queue
latency, runner availability under org concurrency caps, and frugality
habits that matter the day the org loses public-repo freebies. Landed now
deliberately: workflows are inert until #790's org switch flips, so
restructuring carries zero mid-flight risk.

The ngit twin is untouched (its runner cost is lab infra, not GitHub's).
Known trivial conflict with open #775 (clean-container job inserts where
the happy-path block moved from) — both branches are mine to reconcile.

Refs #816
@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Local CI gate — PR #775: test(contract): four drift fences — fragment identity, toolchain ceiling, SSID matcher table, clean-container lane

Run of scripts/pr-verify.sh (the local CI gate: GitHub Actions is
org-blocked and the ngit lane fires only on maintainer mirror pushes,
so a PR ref gets no CI runs — this comment is the offline verification
evidence, produced on the pinned head SHA).

  • Head: drift-fences-767 @ c34ea92842
  • Base: main @ d77e8ca624
  • Gate: pr-verify 294317251383 on ai-legion, started 2026-10-18 12:30 UTC, wall 436s
  • Logs: /tmp/tollgate-pr-verify/775-20261018T122302Z (on the verifying host)
Leg Result Detail
hygiene PASS no conflict markers in the tracked tree (0s)
contract-fences PASS 12 checks green; 1 skipped (absent from tree: entry-UI marker path fence) (3s)
packaging-sequential PASS 12 suites (7s)
shell-suites FAIL red (2/18): uci-defaults-entry-ui-mapping_test.sh ngit-ci-trigger_test.sh — logs in /tmp/tollgate-pr-verify/775-20261018T122302Z/shell-suites (48s)
packaging-concurrent PASS 12 suites, all at once (3s)
go-battery PASS 16 modules, gofmt/vet/build/-race (375s)

Verdict: 1 leg(s) RED — 5 green.

What this gate does not run (no vacuous green)
  • spec-quote drift check (greatspectations tool + pinned nuts fetch)
  • reproducibility / conformance lanes (make release-check territory)
  • router-visible behaviour (PRTA / bench — unit green ≠ router works)

Note: the ngit trigger suite red carries the nak key public
nsec-arg signature — the portability break the NGIT_TRUST
generalization fixed. Trees predating that change reproduce it
on this seat; it is environmental, not PR-caused.

failing-leg log tails
shell-suites — tail
--- ngit-ci-trigger_test.sh.log
ok   - refuses a workflow file that is absent from the worktree
ok   - refuses a workflow that is not present at the declared commit
ok   - refuses a signing key that is not the maintainer key
ok   - never prints the private key on refusal
ok   - refuses a key file with no nsec in it
invalid hex key: '0nsec1d9cg9d8s72u4xrv0g77uczdh8xrrl7f9qhc74ks8pq8lj8a080vsw7jw9e' is not valid hex: encoding/hex: invalid byte: U+006E 'n'
ok   - prints the sha256 of the file's content at the declared commit
FAIL - fails loudly when every relay refuses the event
       rc=1 out=workflow : .ngit/act/workflows/fake.yml
commit   : 3b4cadd6caba612790188016a73d75caef76810a
sha256   : 0a2be7daf3741ba278eca69d6d25755580a1f4223c3c808b5e7e4d621e953544
ERROR: /tmp/tmp.XDaVr8WOC3/wrong.key is not the maintainer key (signer ..., expected 9cd14d9acdbab7ca...)
ok   - never prints the private key on the happy path

8 passed, 1 failed
--- uci-defaults-entry-ui-mapping_test.sh.log
== F. the marker is honoured on a same-version reinstall too
ok   reinstall: the driver exits 0
ok   reinstall (board): uhttpd.main (LuCI) is on the secondary :8090 pair
ok   reinstall (board): uhttpd.main (LuCI) is on the secondary :8443 pair
ok   reinstall (board): uhttpd.main holds NO entry-pair listener
ok   reinstall (board): uhttpd.main's home is /www
ok   reinstall (board): uhttpd.admin (the board) is on the entry :8080 pair
ok   reinstall (board): uhttpd.admin (the board) is on the entry :443 pair
ok   reinstall (board): the entry board keeps its webroot and error_page
ok   reinstall (board): exactly one section binds :8080 (admin)
ok   reinstall (board): exactly one section binds :443 (admin)
ok   reinstall (board): exactly one section binds :8090 (main)
ok   reinstall (board): exactly one section binds :8443 (main)

tests: 126 passed, 9 failed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:post-0.6.0 Deferred until after the v0.6.0 release status:changes-requested Reviewer flagged blocking items; author action needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Four drift fences for tests/contract/ (+ test.yml clean-container lane + pre-commit wiring)

3 participants